﻿using MusicExplorer.Model;
using MusicExplorer.Repository.Internals;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace MusicExplorer.Repository
{
    public abstract class RepositoryBase
    {
        protected readonly Model.Repository m_repository;
        private static IDownloader m_downloader;

        private static Dictionary<Guid, RepositoryBase> s_instance = new Dictionary<Guid, RepositoryBase>();
        public static async void InitRepositories(IDownloader downloader)
        {
            m_downloader = downloader;

            await Task.Run(() =>
                {
                    var currentAssembly = typeof(RepositoryBase).GetTypeInfo().Assembly;
                    var types = currentAssembly.DefinedTypes.Where(t => t.IsSubclassOf(typeof(RepositoryBase)));
                    foreach (var type in types)
                    {
                        var idAttribute = (IdAttribute)type.GetCustomAttribute(typeof(IdAttribute));

                        var assemblyName = currentAssembly.FullName.Split(',')[0];
                        var imagePath = string.Format("{0}.Resources.Images.{1}.repository.png",
                            assemblyName, type.Name);
                        var iconPath = string.Format("{0}.Resources.Images.{1}.icon.png",
                            assemblyName, type.Name);

                        // Create repository
                        var repository = new Model.Repository()
                        {
                            Id = idAttribute.Id,
                            ClassName = type.Name,
                            Name = type.Name.Replace("Repository", ""),
                            Image = currentAssembly.GetManifestResourceStream(imagePath).ReadToEnd(),
                            Icon = currentAssembly.GetManifestResourceStream(iconPath).ReadToEnd()
                        };

                        s_instance[repository.Id] = (RepositoryBase)type.DeclaredConstructors.First().Invoke(new object[] { repository });
                    }
                });
        }

        public static RepositoryBase GetInstance(Model.Repository repository)
        {
            return s_instance[repository.Id];
        }

        public static IEnumerable<RepositoryBase> GetAllInstances()
        {
            return s_instance.Values;
        }

        public static Model.Repository GetRepository(Guid id)
        {
            return s_instance[id].Repository;
        }

        public static IEnumerable<Model.Repository> GetAllRepositories()
        {
            return s_instance.Values.Select(r => r.Repository);
        }

        protected readonly Dictionary<QueryDefinition, string> m_urlTracks;
        protected readonly Dictionary<EntityType, string> m_urlAllEntities;
        protected readonly Dictionary<EntityType, string> m_urlSearchEntities;

        public RepositoryBase(Model.Repository repository)
        {
            m_repository = repository;
            m_urlTracks = new Dictionary<QueryDefinition, string>();
            m_urlAllEntities = new Dictionary<EntityType, string>();
            m_urlSearchEntities = new Dictionary<EntityType, string>();
        }

        public Model.Repository Repository { get { return m_repository; } }

        protected async Task<string> GetOnlineSourceAsync(string url, CancellationToken token)
        {
            try
            {
                token.ThrowIfCancellationRequested();
                return await RepositoryBase.m_downloader.GetOnlineSourceAsync(url, token);
            }
            catch
            {
                return string.Empty;
            }
        }

        public bool SupportQuery(QueryDefinition definition)
        {
            return m_urlTracks.Keys.Contains(definition);
        }

        public IEnumerable<QueryDefinition> SupportedQueries
        {
            get { return m_urlTracks.Keys.AsEnumerable(); }
        }

        public virtual Dictionary<QueryDefinition, IEnumerable<string>> UnsupportedEntities { get { return null; } }
        public abstract int StartingPage { get; }
        public abstract bool IsMusicStore { get; }

        protected abstract List<Track> DecodeTracks(QueryType type, Entity entity, string source);
        protected virtual List<Genre> DecodeGenres(string source) { throw new NotSupportedException(); }
        protected virtual List<Label> DecodeLabels(string source) { throw new NotSupportedException(); }
        protected virtual List<Artist> DecodeArtists(string source) { throw new NotSupportedException(); }
        protected virtual List<Youtube> DecodeYoutube(string source, CancellationToken token) { throw new NotSupportedException(); }
        protected virtual List<Soundcloud> DecodeSoundcloud(string source) { throw new NotSupportedException(); }

        private async Task<List<Entity>> DecodeEntitiesAsync(EntityType type, string source, CancellationToken token)
        {
            return await Task<List<Entity>>.Factory.StartNew(() =>
            {
                if (string.IsNullOrEmpty(source)) return new List<Entity>();

                switch (type)
                {
                    case EntityType.Artist:
                        return DecodeArtists(source).ToList<Entity>();
                    case EntityType.Genre:
                        return DecodeGenres(source).ToList<Entity>();
                    case EntityType.Label:
                        return DecodeGenres(source).ToList<Entity>();
                    case EntityType.Youtube:
                        return DecodeYoutube(source, token).ToList<Entity>();
                    case EntityType.Soundcloud:
                        return DecodeSoundcloud(source).ToList<Entity>();
                    default:
                        throw new NotSupportedException("Unable to decode this type");
                }
            });
        }
        
        protected virtual string GetAllEntitiesUrl(EntityType type)
        {
            return m_urlAllEntities[type];
        }

        protected virtual string GetSearchEntitiesUrl(EntityType type, string terms, int page)
        {
            return string.Format(m_urlSearchEntities[type], terms, page);
        }

        protected virtual string GetTracksUrl(QueryType queryType, Entity entity, int page)
        {
            return string.Format(m_urlTracks[new QueryDefinition(queryType, entity.Type)],
                entity.Id, page);
        }

        private Dictionary<EntityType, List<Entity>> m_entities = new Dictionary<EntityType,List<Entity>>();
        public async Task<List<Entity>> GetAllEntitiesAsync(EntityType type, CancellationToken token)
        {
            if (m_entities.ContainsKey(type)) return m_entities[type];

            try
            {
                token.ThrowIfCancellationRequested();
                var source = await GetOnlineSourceAsync(GetAllEntitiesUrl(type), token);
                token.ThrowIfCancellationRequested();

                // Avoid exception
                if (string.IsNullOrEmpty(source)) return new List<Entity>();

                var entities = await DecodeEntitiesAsync(type, source, token);
                m_entities[type] = entities;
                return entities;
            }
            catch(Exception ex)
            {
                return new List<Entity>();
            }
        }

        private int m_currentEntityPosition;
        public async Task<List<Entity>> SearchEntitiesAsync(EntityType type, string terms, int page, CancellationToken token)
        {
            try
            {
                token.ThrowIfCancellationRequested();
                if (type.SupportAllInOne())
                {
                    if (page == StartingPage)
                    {
                        var entities = await GetAllEntitiesAsync(type, token);
                        return entities.Where(e => e.Name.ToLower().StartsWith(terms.ToLower())).ToList();
                    }
                    else return null;
                }
                else
                {
                    if(page == StartingPage)
                    {
                        m_currentEntityPosition = 0;
                    }

                    var source = await GetOnlineSourceAsync(GetSearchEntitiesUrl(type, terms, page), token);
                    token.ThrowIfCancellationRequested();

                    // Avoid exception
                    if (string.IsNullOrEmpty(source)) return new List<Entity>();

                    var entities = await DecodeEntitiesAsync(type, source, token);
                    foreach(var entity in entities)
                    {
                        entity.Position = ++m_currentEntityPosition;
                    }
                    return entities;
                }
            }
            catch (Exception ex)
            {
                return new List<Entity>();
            }
        }

        private int m_currentTrackPosition;
        public async Task<List<Track>> GetTracksAsync(QueryType queryType, Entity entity, int page, CancellationToken token)
        {
            try
            {
                if(page == StartingPage)
                {
                    m_currentTrackPosition = 0;
                }

                token.ThrowIfCancellationRequested();
                var source = await GetOnlineSourceAsync(GetTracksUrl(queryType, entity, page), token);
                token.ThrowIfCancellationRequested();

                // Avoid exception
                if (string.IsNullOrEmpty(source)) return new List<Track>();

                var tracks = await Task<List<Track>>.Factory.StartNew(() => DecodeTracks(queryType, entity, source));
                foreach(var track in tracks)
                {
                    track.Position = ++m_currentTrackPosition;
                }
                return tracks;
            }
            catch(Exception ex)
            {
                return new List<Track>();
            }
        }

        #region OLD
        //protected async Task<string> GetLocalSourceAsync(Tuple<QueryType, EntityType> url)
        //{
        //    return string.Empty;
            //var className = this.GetType().Name;
            //string fileName = string.Empty;
            //switch(url.EntityType)
            //{
            //    case Url.Genres:
            //        fileName = "genres.html";
            //        break;
            //    case Url.TracksByGenre:
            //        fileName = "tracks.html";
            //        break;
            //    default:
            //        throw new Exception("Local source type not yet supported.");
            //}
            //var path = string.Format("MusicExplorer.Repository.Data.{0}.{1}", className, fileName);

            //var currentAssembly = System.Reflection.Assembly.Load(new System.Reflection.AssemblyName("MusicExplorer.Repository"));
            //var stream = currentAssembly.GetManifestResourceStream(path);
            //using(var reader = new StreamReader(stream))
            //    return await reader.ReadToEndAsync();
        //}

        //public async Task<List<Track>> GetTracksByGenreAsync(string genreId, int page, CancellationToken token)
        //{
        //    try
        //    {
        //        if (token.IsCancellationRequested) return null;
                
        //        var source = await GetOnlineSourceAsync(GetUrl( string.Format(m_urls[Url.TracksByGenre], genreId, page), token);

        //        if (token.IsCancellationRequested) return null;
        //        return await DecodeTracksAsync(source);
        //    }
        //    catch(Exception ex)
        //    {
        //        if (token.IsCancellationRequested) return null;
        //        throw ex;
        //    }
        //}

        

        //private List<Genre> m_genres;
        //public async Task<List<Genre>> GetGenresAsync(CancellationToken token, bool useLocalStorage = false)
        //{
        //    // Caching genres
        //    if (m_genres != null) return m_genres;

        //    try
        //    {
        //        string source = string.Empty;
        //        if (useLocalStorage)
        //        {
        //            if (token.IsCancellationRequested) return null;
        //            source = await GetLocalSourceAsync(Url.Genres);
        //        }
        //        else
        //        {
        //            if (token.IsCancellationRequested) return null;
        //            source = await GetOnlineSourceAsync(m_urls[Url.Genres], token);
        //        }

        //        if (token.IsCancellationRequested) return null;
        //        m_genres = await DecodeGenresAsync(source);
        //        return m_genres;
        //    }
        //    catch(Exception ex)
        //    {
        //        if (token.IsCancellationRequested) return null;
        //        throw ex;
        //    }
        //}

        #endregion
    }
}
